package org.feng.fxfront.util;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import org.feng.api.PageBean;

import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 使用Gson处理Json数据格式的工具类
 *
 * @version V1.0
 * @author: junzi
 * @date: 2022年02月09日 17时05分
 */
public class GsonUtil {
    /**
     * 不会序列化空字段的Gson对象
     */
    private static final Gson GSON = new GsonBuilder().create();

    private static final Gson MUTIL_GSON = mutilGson();
    /**
     * 可以序列化空字段的Gson对象
     */
    private static final Gson GSON_WITH_NULL = new GsonBuilder().serializeNulls().create();

    private static final Gson GSON_WITH_DISABLE_HTML_ESCAPING = new GsonBuilder().disableHtmlEscaping().create();

    public static Gson gson() {
        return GSON;
    }

    /**
     * 转换对象为json字符串（不会序列化空字段）
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJson(Object object) {
        return GSON.toJson(object);
    }

    /**
     * 转换对象为json字符串（不会序列化空字段）
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String specialToJson(Object object) {
        return MUTIL_GSON.toJson(object);
    }


    /**
     * 转换对象为json字符串（可以序列化空字段）
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJsonWithNull(Object object) {
        return GSON_WITH_NULL.toJson(object);
    }

    /**
     * 转换对象为json字符串（禁止html转义）
     *
     * @param object 目标对象
     * @return 一个json字符串
     */
    public static String toJsonWithDisableTtmlEscaping(Object object) {
        return GSON_WITH_DISABLE_HTML_ESCAPING.toJson(object);
    }

    public static <T> T fromJson(String jsonStr, Class<T> clazz) {
        return GSON.fromJson(jsonStr, clazz);
    }

    public static <T> PageBean<T> toPageBean(String jsonStr, Class<T> beanClass) {
        PageBean<Map<String, Object>> pageBean = MUTIL_GSON.fromJson(jsonStr, new TypeToken<PageBean<Map<String, Object>>>() {
        }.getType());

        PageBean<T> result = new PageBean<>();
        result.setPages(pageBean.getPages());
        result.setPageNum(pageBean.getPageNum());
        result.setTotal(pageBean.getTotal());
        result.setPageSize(pageBean.getPageSize());

        // 存在数据
        if (pageBean.getData() != null && !pageBean.getData().isEmpty()) {
            String jsonData = MUTIL_GSON.toJson(pageBean.getData());
            Type type = listBeanType(beanClass);
            List<T> list = MUTIL_GSON.fromJson(jsonData, type);
            result.setData(list);
        }
        return result;
    }

    private static <T> Type listBeanType(Class<T> beanClass) {
        return new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                Type[] types = new Type[1];
                types[0] = beanClass;
                return types;
            }

            @Override
            public Type getRawType() {
                return List.class;
            }

            @Override
            public Type getOwnerType() {
                return null;
            }
        };
    }

    /**
     * 校验字符串是否是一个json格式
     * <br> 注意：{@code "{}"} 也是符合条件的json
     *
     * @param jsonStr 目标字符串
     * @return true表示目标是一个正确的json格式
     */
    public static boolean validateJson(String jsonStr) {
        JsonElement jsonElement;
        try {
            jsonElement = JsonParser.parseString(jsonStr);
        } catch (Exception e) {
            return false;
        }
        if (jsonElement == null) {
            return false;
        }
        return jsonElement.isJsonObject();
    }

    /**
     * 将一个json字符串转换为 Map。（不适用与json数组）<br><b>本方法适用于简单层级的json串。</b>
     * <br>
     * 注意：所有的数值类型，适用Object接收时，会自动转换为 double 类型
     *
     * @param jsonStr json字符串
     * @return 一个Map对象
     */
    public static Map<String, Object> parseToObjectMap(String jsonStr) {
        return gson().fromJson(jsonStr, new TypeToken<Map<String, Object>>() {
        }.getType());
    }

    /**
     * 将一个json字符串转换为 Map。（不适用与json数组）<br><b>本方法适用于简单层级的json串。</b>
     * <br>
     * 注意：所有的数值类型，会按照原有类型转换。
     *
     * @param jsonStr json字符串
     * @return 一个Map对象
     */
    public static Map<String, String> parseToStringMap(String jsonStr) {
        return gson().fromJson(jsonStr, new TypeToken<Map<String, String>>() {
        }.getType());
    }

    private GsonUtil() {
    }


    public static Gson mutilGson() {
        GsonBuilder builder = new GsonBuilder()
                .setLenient()
                .disableHtmlEscaping()
                .serializeNulls()
                .excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC, Modifier.FINAL, Modifier.NATIVE, Modifier.PROTECTED)
                .enableComplexMapKeySerialization();
        dateTypeAdapter(builder);
        return builder.create();
    }

    private static void dateTypeAdapter(GsonBuilder builder) {
        String dFmt = "yyyy-MM-dd";
        String dtFmt = "yyyy-MM-dd HH:mm:ss";
        builder.registerTypeAdapter(LocalDateTime.class, (JsonSerializer<LocalDateTime>) (src, type, ctx) -> Objects.isNull(src) ? JsonNull.INSTANCE : new JsonPrimitive(TimeUtil.parse(src)));
        builder.registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, type, ctx) -> handleLocalDateTime(json, dtFmt));
        builder.registerTypeAdapter(LocalDate.class, (JsonSerializer<LocalDate>) (src, type, ctx) -> Objects.isNull(src) ? JsonNull.INSTANCE : new JsonPrimitive(DateTimeFormatter.ofPattern(dFmt).format(src)));
        builder.registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>) (json, type, ctx) -> handleLocalDate(json, dFmt));
        builder.registerTypeAdapter(Date.class, (JsonSerializer<Date>) (src, type, ctx) -> Objects.isNull(src) ? JsonNull.INSTANCE : new JsonPrimitive(new SimpleDateFormat(dtFmt).format(src)));
        builder.registerTypeAdapter(Date.class, (JsonDeserializer<Date>) (json, type, ctx) -> handleDate(json, dtFmt));
    }

    private static LocalDateTime handleLocalDateTime(JsonElement json, String format) {
        if (Objects.isNull(json)) {
            return null;
        }
        if (json.isJsonPrimitive()) {
            if (StringUtil.isEmpty(json.getAsString().trim())) {
                return null;
            }
            return LocalDateTime.parse(json.getAsString().trim(), DateTimeFormatter.ofPattern(format));
        } else if (json.isJsonObject()) {
            JsonObject obj = json.getAsJsonObject();
            JsonObject dObj = obj.get("date").getAsJsonObject();
            JsonObject tObj = obj.get("time").getAsJsonObject();
            return LocalDateTime.of(dObj.get("year").getAsInt(), dObj.get("month").getAsInt(), dObj.get("day").getAsInt(),
                    tObj.get("hour").getAsInt(), tObj.get("minute").getAsInt(), tObj.get("second").getAsInt(), tObj.get("nano").getAsInt());
        }
        return null;

    }

    private static LocalDate handleLocalDate(JsonElement json, String format) {
        if (Objects.isNull(json)) {
            return null;
        }
        if (json.isJsonPrimitive()) {
            return LocalDate.parse(json.getAsString().trim(), DateTimeFormatter.ofPattern(format));
        } else if (json.isJsonObject()) {
            JsonObject obj = json.getAsJsonObject();
            return LocalDate.of(obj.get("year").getAsInt(), obj.get("month").getAsInt(), obj.get("day").getAsInt());
        }
        return null;
    }

    @SuppressWarnings("all")
    private static Date handleDate(JsonElement json, String format) {
        try {
            if (Objects.isNull(json)) {
                return null;
            }
            String d = json.getAsString().trim();
            // 默认格式
            if (d.endsWith("AM") || d.endsWith("PM")) {
                return new Date(d);
            }
            return new SimpleDateFormat(format).parse(d);
        } catch (ParseException e) {
            return null;
        }
    }
}
